Skip to content

Update OverlayTiming to support BIB random mixing - #413

Open
madbaron wants to merge 22 commits into
key4hep:mainfrom
madbaron:add_overlay_BIB_random_mix
Open

Update OverlayTiming to support BIB random mixing#413
madbaron wants to merge 22 commits into
key4hep:mainfrom
madbaron:add_overlay_BIB_random_mix

Conversation

@madbaron

@madbaron madbaron commented Jul 6, 2026

Copy link
Copy Markdown
Member

BEGINRELEASENOTES

  • Extend OverlayTiming with random background-file mixing: the new RandomMixBackgroundFiles option treats each file in a background group as an independent event source and picks a random set of files for every overlaid event. BackgroundFileNames entries may now be directories (their .root files are used).
  • Add the MergeMCParticles option to OverlayTiming (default true); when false, background MCParticles are not stored, tracker hits keep the momentum of their originating particle and calorimeter contributions get an empty particle.
  • Serialize all background ROOT I/O on a dedicated worker thread so OverlayTiming is safe to run with intra-event multithreading.

ENDRELEASENOTES

This PR updates OverlayTiming with the logic used by the muon collider software to overlay the BIB pseudo-events (from https://github.com/MuonColliderSoft/k4Reco/blob/main/k4Reco/Overlay/components/OverlayTimingRandomMix.cpp).
I opted for porting the changes over rather than asking to include a second algorithm, since the code was 95% the same.

The updated algorithm uses TBB for intra-event multithreading in processing the thousands of inputs for the overlay.

@madbaron

madbaron commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

The downstream build failure doesn't seem related to the changes - but I can't retrigger it.

@andread3vita

Copy link
Copy Markdown

Hi! I tried this implementation, and if I simply set overlay.RandomMixBackgroundFiles = True, the overlay works using just a single background file instead of 40:

from Gaudi.Configuration import INFO

from k4FWCore import ApplicationMgr
from k4FWCore import IOSvc
from Configurables import EventDataSvc
from Configurables import OverlayTiming
from Configurables import UniqueIDGenSvc

from pathlib import Path


background_base = Path("/eos/experiment/fcc/ee/simulation/key4hep_2026_04_20/91GeV/IDEA_o1_v03/IPC_Z_background")
background_file_list = []
for d in sorted(background_base.iterdir()):
    background_file_list.append(str(d))
            
id_service = UniqueIDGenSvc("UniqueIDGenSvc")
eds = EventDataSvc("EventDataSvc")
iosvc = IOSvc()
# iosvc.Input = "/afs/cern.ch/user/a/aloeschc/fcc_fullsim_testing_grounds/data/IDEA/IDEA_o1_v03/physics_events/p8_ee_Z_qqbar_ud/1000_91.188GeV_ISR_FSR/000/IDEA_o1_v03_1000_p8_ee_Z_qqbar_ud_91.188GeV_ISR_FSR.root"
iosvc.Input = "/afs/cern.ch/work/a/adevita/public/testBIB/IDEA_test/muonGunIDEAv3o1.root" 

iosvc.Output = "IDEA_o1_v03_OverlayIPC_test.root"

overlay = OverlayTiming()
overlay.MCParticles = "MCParticles"
overlay.BackgroundMCParticleCollectionName = "MCParticles"
overlay.SimTrackerHits = ["DCHCollection", "MuonSystemCollection", "SiWrDCollection", "SiWrBCollection", "VertexBarrelCollection", "VertexEndcapCollection", "PreshowerSystemCollection"]
overlay.SimCalorimeterHits = []
overlay.OutputSimTrackerHits = ["OverlayDCHCollection", "OverlayMuonSystemCollection", "OverlaySiWrDCollection", "OverlaySiWrBCollection", "OverlayVertexBarrelCollection", "OverlayVertexEndcapCollection", "OverlayPreshowerSystemCollection"]
overlay.OutputSimCalorimeterHits = []
overlay.OutputMCParticles = "OverlayMCParticles"
overlay.OutputCaloHitContributions = []
overlay.AllowReusingBackgroundFiles = True
overlay.CopyCellIDMetadata = True
overlay.NBunchtrain = 41          # total BX in train
overlay.NumberBackground = [1]    # one background event per BX
overlay.Delta_t = 20              # ns between BX
overlay.PhysicsBX = 21            # puts physics at 21 with 20 before & 30 after (allow for hits 200ns after event time)
overlay.Poisson_random_NOverlay = [False]
overlay.StartBackgroundEventIndex = -1
# overlay.BackgroundFileNames = [
#       background_file_list
# ]

overlay.RandomMixBackgroundFiles = True
overlay.BackgroundFileNames = [["/eos/experiment/fcc/ee/simulation/key4hep_2026_04_20/91GeV/IDEA_o1_v03/IPC_Z_background"]]

overlay.TimeWindows = {"MCParticles": [-400, 400], "DCHCollection": [-400, 400], "MuonSystemCollection": [-20, 0], "SiWrDCollection": [-20, 0],"SiWrBCollection": [-20, 0], "VertexBarrelCollection": [-20, 0],"VertexEndcapCollection": [-20, 0], "PreshowerSystemCollection": [-20, 0]}

iosvc.outputCommands = ["drop *", "keep OverlayDCHCollection*", "keep OverlaySiWrDCollection*", "keep OverlaySiWrBCollection*", "keep OverlayVertexBarrelCollection*", "keep OverlayVertexEndcapCollection*", "keep OverlayMC*", "keep *EventHeader*"]


ApplicationMgr(TopAlg=[overlay],
               EvtSel="NONE",
               EvtMax=1,
               ExtSvc=[eds],
               OutputLevel=INFO,
               )

@ArinaPon

Copy link
Copy Markdown

Hello! while testing the implementation, I think I might have an idea why only one background file was being used across the bunch train.

fileIndices is shuffled before the BX loop, but k starts again from 0 for every BX. With NumberBackground = [1], every BX therefore selects:

fileIndices[0]

And I guess this means that the same randomly selected background file is reused for all BXs of the signal event.

I tried adding a counter outside the BX loop:

size_t fileCursor = 0;

and changing the file selection to:

const int fileIndex =
    m_randomMix ? fileIndices[fileCursor++ % fileIndices.size()] : 0;

With this change, the code continues through the shuffled list instead of starting again from the first file for every BX.

I rebuilt and tested this with 120 IPC background files and the overlay completed successfully, the different events showed different background patterns.

@madbaron

Copy link
Copy Markdown
Member Author

Thanks for the checks @andread3vita and the suggestion for a fix @ArinaPon.
I ended up implementing it ~1:1, with the main difference that on wrap of the fileCursor I reshuffle so that there is no recurring pattern in overlaid background events.

Comment thread doc/OverlayTiming.md
Comment on lines +117 to +119
With many large background files the algorithm is dominated by reading and
decompressing them. Set `OverlayThreads` to a value greater than 1 to read and
decompress the background files of a single event on several threads:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conceptually this might interfere with Gaudis internal scheduling (even if we also use tbb to do our multithreading). It's unclear to me whether the Gaudi internal tbb bits communicate with the tbb bits here.

There is precedent for doing this though as the CKF in k4ActsTracking also does some internal multithreading. This might need some policy discussion as it could imply different usage patterns for different community (e.g. run the general chain on a single thread but branch out to multi-threading in dedicated algorithms vs. running the full chain on multiple threads with Gaudi scheduling but no algorithm-internal multi-threading).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. In the ideal world you might want to allow users to do a combination of both, if possible.
For now, especially in colliders that are computationally challenging per event, being able to use MT inside the same event is much more important than multi-threading over events, which can be done trivially in batch jobs anyway.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @jmcarcell knows if functional algorithms can already propagate that to the Gaudi scheduler somehow. Otherwise the potential interplay will for now just be another thing to document.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the help of Claude, I had a look at the Gaudi sources. My understanding is that there's no way for a functional algorithm to declare or hand its internal parallelism to the scheduler. But the two TBB layers aren't independent either.

The good news is we can't oversubscribe the machine; the bad news is the scheduler counts algorithms in flight, not threads, so it keeps dispatching while we're fanned out and conversely with ThreadPoolSize=1 the pipeline only gets ~2 threads regardless of OverlayThreads (ntokens bounds in-flight items, not concurrency).

One thing I've added as a precaution: the pipeline now runs inside tbb::this_task_arena::isolate(). The reasoning is that while the calling thread blocks in parallel_pipeline, TBB may steal another AlgTask onto it, and AlgTask::operator() sets the thread-local EventContext and calls whiteboard()->selectStore(slot) without restoring either. While I haven't observed this, it costs nothing, so I'd rather keep it than rely on arena timing.

I've documented the OverlayThreads / ThreadPoolSize interplay in doc/OverlayTiming.md: they draw from one pool, OverlayThreads > 1 pays off when ThreadPoolSize is small, and raising both just repartitions the same threads.

Comment thread k4FWCore/components/OverlayTiming.h
Comment thread k4FWCore/components/OverlayTiming.h
Comment thread k4FWCore/CMakeLists.txt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants